#!/usr/bin/env python
import webapp2
import cgi
import urllib
import random
from copy import deepcopy

from google.appengine.ext import db
from google.appengine.api import users
import jinja2
import os

jinja_environment = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))

NUM_COL = 10
NUM_ROW = 10

debug = True

bot_status = "Human scum. Prepare to Die. Beep Boop."
my_status = "All ships ready and waiting, commander."
bot_board = []
my_board = []
bot_ships = []
my_ships = []

prev_bot_status = "Human scum. Prepare to Die. Beep Boop."
prev_my_status = "All ships ready and waiting, commander."
prev_bot_board = []
prev_my_board = []
prev_bot_ships = []
prev_my_ships = []

class Ship(db.Model):
    name = db.StringProperty()
    length = db.IntegerProperty()
    mark = db.StringProperty()
    left = db.IntegerProperty()
    right = db.IntegerProperty()
    top = db.IntegerProperty()
    bottom = db.IntegerProperty()
    is_vert = db.BooleanProperty()
    state = db.ListProperty(str)

    def get_name(self):
        return self.name

    def draw_ship(self, board):
        if not self.is_vert:
            row = self.top
            for idx in range(self.length):
                col = idx + self.left
                board[row][col] = self.state[idx]
        else:
            col = self.left
            for idx in range(self.length):
                row = idx + self.top
                board[row][col] = self.state[idx]

    def is_sunk(self):
        for mark in self.state:
            if mark == self.mark:
                return False

        return True

    def handle_salvo(self, row, col):

        if col < self.left or col > self.right:
            return False
        if row < self.top or row > self.bottom:
            return False

        if self.is_vert:
            idx = row - self.top
        else:
            idx = col - self.left

        self.state[idx] = 'X'
        return True

    def is_valid_pos(self, ships):

        if self.left < 0 or self.right>=NUM_ROW:
            return False
        if self.top < 0 or self.bottom>=NUM_COL:
            return False
        for ship in ships:
            if self.is_intersecting(ship):
                return False

        return True
            
    def is_intersecting(self, other):
        if self.left<=other.right and self.right>=other.left:
            if self.top<=other.bottom and self.bottom>=other.top:
                return True
        return False
            
    def print_state(self):
        if self.is_sunk():
            return self.name + " is sunk"
        else:
            for idx in range(self.length):
                if self.state[idx] == 'X':
                    return self.name + " is hit"
            return self.name + " is in tip-top shape"

def init_game():
    global bot_status
    global my_status
    global bot_board
    global my_board
    global bot_ships
    global my_ships

    global prev_bot_status
    global prev_my_status
    global prev_bot_board
    global prev_my_board
    global prev_bot_ships
    global prev_my_ships

    bot_status = "Human scum. Prepare to Die. Beep Boop."
    my_status = "All ships ready and waiting, commander."
    bot_board = []
    my_board = []
    bot_ships = []
    my_ships = []

    prev_bot_status = "Human scum. Prepare to Die. Beep Boop."
    prev_my_status = "All ships ready and waiting, commander."
    prev_bot_board = []
    prev_my_board = []
    prev_bot_ships = []
    prev_my_ships = []

    bot_ships = get_random_ships()
    my_ships = get_random_ships()

    for idx in range(0, NUM_ROW):
        bot_board.append(new_col())
        my_board.append(new_col())

    prev_bot_status = bot_status
    prev_my_status = my_status
    prev_bot_board = deepcopy(bot_board)
    prev_my_board = deepcopy(my_board)
    prev_bot_ships = deepcopy(bot_ships)
    prev_my_ships = deepcopy(my_ships)

    
def new_col():
    return ['.'] * NUM_COL


def get_random_ships():
    ships = []
    #emulate do-while loop
    carrier = Ship()
    options = {"name": "carrier", "mark": "c",
            "top": 0, "left": 0, "length": 5, "is_vert": True}
    while True:
        pos = get_rand_pos()
        options["top"] = pos[0]
        options["left"] = pos[1]
        options["is_vert"] = pos[2]
        init_ship(carrier, options)
        if carrier.is_valid_pos(ships):
            ships.append(carrier)
            break

    battleship = Ship()
    options = {"name": "battleship", "mark": "b",
               "top": 0, "left": 0, "length": 4, "is_vert": True}
    while True:
        pos = get_rand_pos()
        options["top"] = pos[0]
        options["left"] = pos[1]
        options["is_vert"] = pos[2]
        init_ship(battleship, options)
        if battleship.is_valid_pos(ships):
            ships.append(battleship)
            break

    destroyer = Ship()
    options = {"name": "destroyer", "mark": "d",
                  "top": 0, "left": 0, "length": 3, "is_vert": True}
    while True:
        pos = get_rand_pos()
        options["top"] = pos[0]
        options["left"] = pos[1]
        options["is_vert"] = pos[2]
        init_ship(destroyer, options)
        if destroyer.is_valid_pos(ships):
            ships.append(destroyer)
            break

    submarine = Ship()
    options = {"name": "submarine", "mark": "s",
              "top": 0, "left": 0, "length": 3, "is_vert": True}

    while True:
        pos = get_rand_pos()
        options["top"] = pos[0]
        options["left"] = pos[1]
        options["is_vert"] = pos[2]
        init_ship(submarine, options)
        if submarine.is_valid_pos(ships):
            ships.append(submarine)
            break

    cruiser = Ship()
    options = {"name": "cruiser", "mark": "r",
                "top": 0, "left": 0, "length": 2, "is_vert": True}
    while True:
        pos = get_rand_pos()
        options["top"] = pos[0]
        options["left"] = pos[1]
        options["is_vert"] = pos[2]
        init_ship(cruiser, options)
        if cruiser.is_valid_pos(ships):
            ships.append(cruiser)
            break
        
    return ships

def init_ship(ship, options):
    ship.name = options["name"]
    ship.length = options["length"]
    ship.mark = options["mark"]
    ship.is_vert = options["is_vert"]
    ship.left = options["left"]
    ship.top = options["top"]
    ship.state = []

    for idx in range(options["length"]):
        ship.state.append(options["mark"])

    if options["is_vert"]:
        ship.right = options["left"]
        ship.bottom = options["top"] + options["length"] - 1
    else:
        ship.right = options["left"] + options["length"] - 1
        ship.bottom = options["top"]

def get_rand_pos():
    row = random.randint(0,NUM_ROW-1)
    col = random.randint(0,NUM_COL-1)
    is_vert = random.choice([True, False])
    return [row, col, is_vert]
    
class MainPage(webapp2.RequestHandler):

    def print_board(self, board, ships=[]):
        for ship in ships:
            ship.draw_ship(board)
        board_strings = []
        row_str = "x "
        for idx in range(0, NUM_COL):
            row_str = row_str + str(idx) + " "
        board_strings.append(row_str)

        for idx in range(NUM_ROW):
            row_str = chr(ord("A") + idx) + " "

            for val in board[idx]:
                row_str = row_str + val + " "

            board_strings.append(row_str)
        return board_strings

    def print_status(self, debug, bot_board, my_board, bot_ships, ships):
        if debug:
            bot_board = self.print_board(bot_board, bot_ships)
        else:
            bot_board = self.print_board(bot_board)
            
        my_board = self.print_board(my_board, ships)

        my_ship_statuses = []
        for ship in ships:
            my_ship_statuses.append(ship.print_state())

        template_values = {
            'bot_status' : bot_status,
            'player_status' : my_status,
            'bot_board': bot_board,
            'my_board': my_board,
            'my_ship_statuses': my_ship_statuses,
        }
        template = jinja_environment.get_template('index.html')
        self.response.out.write(template.render(template_values))


    def get(self):
        global bot_status
        global my_status
        global bot_board
        global my_board
        global bot_ships
        global my_ships

        global prev_bot_status
        global prev_my_status
        global prev_bot_board
        global prev_my_board
        global prev_bot_ships
        global prev_my_ships
        
        self.print_status(debug, bot_board, my_board, bot_ships, my_ships)
    

class MoveCommand(webapp2.RequestHandler):
    def post(self):
        global bot_status
        global my_status
        global bot_board
        global my_board
        global bot_ships
        global my_ships

        global prev_bot_status
        global prev_my_status
        global prev_bot_board
        global prev_my_board
        global prev_bot_ships
        global prev_my_ships

        command = self.request.get('command')
        
        cmds = [None, None]
        if not self.parse_cmd(command, cmds):
            if command == "undo":
                bot_status = prev_bot_status
                my_status = prev_my_status
                bot_board = prev_bot_board
                my_board = prev_my_board
                bot_ships = prev_bot_ships
                my_ships = prev_my_ships
            elif command == "reset":
                init_game()
            else:
                bot_status = "Error. Error."
                my_status = "Sir. We could not comprehend your command, sir."
        else:
            prev_bot_status = bot_status
            prev_my_status = my_status
            prev_bot_board = deepcopy(bot_board)
            prev_my_board = deepcopy(my_board)
            prev_bot_ships = deepcopy(bot_ships)
            prev_my_ships = deepcopy(my_ships)

            [row, col] = cmds
            bot_status = self.handle_bot_move(my_board, my_ships)
            my_status = self.handle_my_move(bot_board, bot_ships, row, col)

            if bot_status:
                self.response.write(bot_status+'\n')
            if my_status:
                self.response.write(my_status+'\n')
        self.redirect('/')

    def parse_cmd(self, cmd, cmds):
        split_cmd = cmd.split(":")
        if len(split_cmd) == 2:
            hit = None
            [row, col] = split_cmd
        else:
            return False

        row = ord(row) - ord("A")
        col = int(col)

        if row < 0 or row >= NUM_ROW:
            return False
        if col < 0 or col >= NUM_COL:
            return False

        cmds[0] = row
        cmds[1] = col
        return True

    def handle_bot_move(self, my_board, ships):
        status = ""
        is_hit = False
        while True:
            [row, col, dummy] = get_rand_pos()
            if not (my_board[row][col] == "X" or my_board[row][col] == "Z"):
                break
        
        for ship in ships:
            if ship.handle_salvo(row, col):
                is_hit = True
                status = "I have HIT my target pathetic human."
                if ship.is_sunk():
                    status = "HIT registerd. Also I SUNK your " + ship.get_name() + ". ha ha ha"

        if not is_hit:
            status = "Target MISSED. Recalibrating"

        if is_hit:
            mark = "X"
        else:
            mark = "Z"

        my_board[row][col] = mark

        return status

    def handle_my_move(self, bot_board, bot_ships, row, col):
        status = ""
        is_hit = False
        for ship in bot_ships:
            if ship.handle_salvo(row, col):
                is_hit = True
                status = "We've got a HIT captain!"
                if ship.is_sunk():
                    status = "Sir, that's a HIT. We've also SUNK their " + ship.get_name()
        if not is_hit:
            status = "Confirmed MISS, sir. What should we do?"

        if is_hit:
            mark = "X"
        else:
            mark = "Z"

        bot_board[row][col] = mark
        return status

app = webapp2.WSGIApplication([('/', MainPage),
                              ('/move', MoveCommand)],
                              debug=True)
init_game()
